package com.platform.framework.controller.trade;

import cn.hutool.core.util.NumberUtil;
import cn.hutool.http.HttpRequest;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.alipay.api.internal.util.AlipaySignature;
import com.platform.framework.entity.alipay.AlipayBusinessInfo;
import com.platform.framework.entity.alipay.AlipaySubaccountInfo;
import com.platform.framework.entity.pub.TradeFlow;
import com.platform.framework.entity.wxpay.WxpayBusinessInfo;
import com.platform.framework.entity.wxpay.WxpayReturnInfo;
import com.platform.framework.entity.wxpay.WxpaySubaccountInfo;
import com.platform.framework.gateway.alipay.config.AlipayConfig;
import com.platform.framework.serviceinf.alipay.AlipayBusinessInfoService;
import com.platform.framework.serviceinf.alipay.AlipayService;
import com.platform.framework.serviceinf.alipay.AlipaySubAccountInfoService;
import com.platform.framework.serviceinf.pub.TradeFlowService;
import com.platform.framework.serviceinf.wxpay.WxpayBusinessInfoService;
import com.platform.framework.serviceinf.wxpay.WxpayReturnInfoService;
import com.platform.framework.serviceinf.wxpay.WxpayService;
import com.platform.framework.serviceinf.wxpay.WxpaySubaccountInfoService;
import com.platform.framework.threads.QueryPayResultThread;
import com.platform.framework.util.net.tool.HttpUtil;
import com.platform.framework.util.net.tool.WebUtil;
import com.platform.framework.util.pub.*;
import com.platform.framework.util.sign.SignUtil;
import com.platform.framework.util.sign.WxSignUtil;
import com.platform.framework.vo.exception.ReceiverException;
import com.platform.framework.vo.info.ExceptionInfo;
import com.platform.framework.vo.pub.ResponseVo;
import com.platform.framework.vo.states.PayGate;
import com.platform.framework.vo.states.PayMethod;
import com.platform.framework.vo.states.PayResult;
import com.platform.framework.vo.states.PayType;
import com.platform.framework.vo.transfer.TransportLayer;
import com.platform.framework.vo.transfer.WxPayRequest;
import org.apache.log4j.Logger;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.View;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.math.BigDecimal;
import java.util.*;

/**
 * 2016/11/7
 * @author ly-liugaoyang
 * 后台--接口
 * 交易服务类
 */
@Controller
@RequestMapping("/sys/trade")
public class TradeController {
	
	private static Logger logger = Logger.getLogger(TradeController.class);
	
	@Resource
	private TradeFlowService tradeFlowService;
	
	@Resource
	private AlipayBusinessInfoService alipayBusinessInfoService;
	
	@Resource
	private WxpayBusinessInfoService wxpayBusinessInfoService;
	
	@Resource
	private AlipaySubAccountInfoService alipaySubAccountInfoService;
	
	@Resource
	private WxpaySubaccountInfoService wxpaySubaccountInfoService;
	
	@Resource
	private AlipayService alipayService;
	
	@Resource
	private WxpayService wxpayService;
	
	@Resource
	private WxpayReturnInfoService wxpayReturnInfoService;

	@CrossOrigin(origins = "*", maxAge = 3600)
	@RequestMapping(value="/customertrade",produces="application/json;charset=UTF-8",method = RequestMethod.POST)
	@ResponseBody
	public ResponseVo customertrade (HttpServletRequest req ,HttpServletResponse rep,@RequestBody TransportLayer transportLayer) throws Exception {
		logger.info("--------------[tradelog:"+transportLayer.getOrderNo()+"] enter customer trade success");
		transportLayer.setUserIp(WebUtil.getIpAddr(req));
		transportLayer.setSpbillCreateIp(WebUtil.getIpAddr(req));
		logger.info("--------------[tradelog:"+transportLayer.getOrderNo()+"] 接受参数："+JsonUtil.Object2Json(transportLayer));
		ResponseVo responseVo = Validation.validationTrans(transportLayer);
		if (!Validation.isEmpty(responseVo)) {
			return responseVo;
		}
		if (checkTradeFail(transportLayer)) {
			return new ResponseVo(PayResult.ERROR, null, ExceptionInfo.RE_PAY_ORDER, null);
		}
		setPayStatus(transportLayer);		//设置交易状态
		setTradeServicer(transportLayer); // 设置服务方（调起方）
		setTradeReceiver(transportLayer); // 设置收款方
		saveTradeFlow(transportLayer); // 保存传过来的信息作为交易流水
		responseVo = tradeService(transportLayer); // 用反射调起交易(支付/退款/查询) 接口都返回一个ResponseVo
		if (isQRCodePay(transportLayer,responseVo,tradeFlowService,rep)) {
			return null;
		}
		return responseVo;
	}

    @CrossOrigin(origins = "*", maxAge = 3600)
    @RequestMapping(value = "/wxH5PayPage/",method = RequestMethod.GET)
    public ModelAndView wxH5PayPage(WxPayRequest wxPayRequest){
        ModelAndView mv = new ModelAndView();
        mv.setViewName("epay/userPages/wxH5pay");
        String openid = wxPayRequest.getOpenid();
        String orderNo = wxPayRequest.getOrderNo();
        String body = wxPayRequest.getBody();
        String totalAmount = wxPayRequest.getTotalAmount();
        String returnUrl = wxPayRequest.getReturnUrl();

        mv.addObject("openid",openid);
        mv.addObject("orderNo",orderNo);
        mv.addObject("body",body);
        mv.addObject("totalAmount",totalAmount);
        mv.addObject("returnUrl",returnUrl);

        return mv;
    }

//	@CrossOrigin(origins = "*", maxAge = 3600)
//	@RequestMapping(value = "/wxH5PayPage/",method = RequestMethod.GET)
//	public ModelAndView wxH5PayPage(HttpServletRequest req){
//        ModelAndView mv = new ModelAndView();
//        mv.setViewName("epay/userPages/wxH5pay");
//
//        String code = req.getParameter("code");
//        String appid = "wx950ef94f07fd3be1";
//
//        JSONObject jsonObject = new JSONObject();
//        jsonObject.put("code",code);
//        jsonObject.put("appid",appid);
//		jsonObject.put("source",1);
////        String s = HttpUtil.executeHttpGet("http://lain001.picp.io/api/wx/auth");
//        String body = HttpRequest.post("http://lain001.picp.io/api/wx/auth")
//                .body(jsonObject)
//                .execute().body();
//
//        String openid = "";
//        if(JSONUtil.isJson(body)){
//            JSONObject result = JSONUtil.parseObj(body);
////            String data = result.get("data") == null? null : result.get("data").toString();
//            Object data1 = result.get("data");
//            if(data1 == null){
//                return mv;
//            }
//            JSONObject dataObj = JSONUtil.parseObj(data1);
//            openid = dataObj.get("openid") == null ? null : dataObj.get("openid").toString();
//            logger.info("openid get success : " + openid);
//        }
//
//        mv.addObject("code",code);
//        mv.addObject("appid",appid);
//        mv.addObject("openid",openid);
//
//        return mv;
//	}

	@GetMapping("/tokenverify")
	public void tokenverify(HttpServletRequest request, HttpServletResponse response) throws Exception {

		// 微信加密签名，signature结合了开发者填写的token参数和请求中的timestamp参数、nonce参数。
		String signature = request.getParameter("signature");
		// 时间戳
		String timestamp = request.getParameter("timestamp");
		// 随机数
		String nonce = request.getParameter("nonce");
		// 随机字符串
		String echostr = request.getParameter("echostr");

		PrintWriter out = response.getWriter();

		// 通过检验signature对请求进行校验，若校验成功则原样返回echostr，表示接入成功，否则接入失败
		if(WxSignUtil.sha1(signature,timestamp,nonce)){
			out.print(echostr);
		}
		// 清空并关闭资源
		out.close();
		out = null;
	}

    /**
     * 静默授权页面
     * 防止跨域调用
     * 所有获取code方法都在本平台内
     * @param req
     * @return
     */
    @RequestMapping(value = "/getWxCodePage",method = RequestMethod.GET)
    public ModelAndView getWxCodePage(HttpServletRequest req){
        ModelAndView mv = new ModelAndView();
        mv.setViewName("epay/userPages/wxH5auth");
        return mv;
    }

	/**
	 * 设置本次交易状态（支付/退款/查询）
	 * @param transportLayer
	 */
	private void setPayStatus(TransportLayer transportLayer) {
		Map<String, String> typeMap = PayMethod.getTypeMap();
		Map<String, String> methodMap = PayMethod.getMethodMap();
		StringBuffer sb = new StringBuffer();
		sb.append(typeMap.get(transportLayer.getPayType())).append(methodMap.get(transportLayer.getPayGate()));
		transportLayer.setReflectMethod(sb.toString());
		logger.info("[tradelog:"+transportLayer.getOrderNo()+"] excute method ： " + sb.toString());
	}
	
	/**
	 * 检查交易
	 * 1、若为支付，则检查该订单是否有支付成功的记录，如果有，则不允许重复下单。 返回true
	 * 2、若为退款，则检查该交易是否部分退款交易，如果是，则设置相应的字段。 返回false
	 * @param transportLayer
	 * @return
	 */
	private boolean checkTradeFail(TransportLayer transportLayer) {
		if (transportLayer.getPayGate().equals(PayGate.QUERY_TRADE_RESULT)) {
			return false;
		}
		
		List<TradeFlow> list = tradeFlowService.getTradeFlowByOrderId(transportLayer.getOrderNo());
		if (Validation.isEmptyList(list)) {
			if (transportLayer.getPayGate().equals(PayGate.TRADE_REFUND)) {
				logger.error("[tradelog] 订单号：" + transportLayer.getOrderNo() + " 还没有付款，不能退款。");
				return true; // 不能调起退款
			}
			return false; // 可以调起支付
		}
		for (TradeFlow tradeFlow : list) {
			if (!Validation.isEmpty(tradeFlow.getPayResult()) && tradeFlow.getPayResult().equals(PayResult.TRADE_SUCCESS)) {
				if (transportLayer.getPayGate().equals(PayGate.TRADE_REFUND)) {
					transportLayer.setOutTradeNo(tradeFlow.getOutTradeNo()); // 设置outTradeNo
					transportLayer.setTotalAmount(tradeFlow.getTotalAmount()); // 订单总金额
					transportLayer.setOutRefundNo(Utils.getRandomNum());  //设置微信退款单号
					double totalAmount = Double.parseDouble(tradeFlow.getTotalAmount());
					double refundAmount = Double.parseDouble(transportLayer.getRefundAmount());
					if (totalAmount < refundAmount) {
						logger.info("[tradelog:"+transportLayer.getOrderNo()+"] --^_^--退款金额输入有误,退款金额不能大于订单总金额--^_^--"); 
					} else if(totalAmount > refundAmount) {
						logger.info("[tradelog:"+transportLayer.getOrderNo()+"] 触发部分退款，订单全额为："+tradeFlow.getTotalAmount() + "，部分退款金额为："+transportLayer.getRefundAmount());
						transportLayer.setOutRequestNo(Utils.getRandomNum()); // 部分退款，则设置out_request_no字段。
					}else{		// 如果金额与订单金额一样，则为全额退款。
						logger.info("[tradelog:"+transportLayer.getOrderNo()+"] 触发全额退款，退款金额："+transportLayer.getRefundAmount());
						}
					return false; // 已经设置好退款参数，可以调起退款
				}
				return true; // 重复下单，不能调起支付
			}
		}
		return false;
	}
	
	/**
	 * 设置调起交易服务方
	 * @param transportLayer
	 */
	private void setTradeServicer(TransportLayer transportLayer) {
		if(transportLayer.getPayType().equals(PayType.ALI_PAY)){
			AlipayBusinessInfo alipayBusinessInfo = alipayBusinessInfoService.getInfoByLoginName(transportLayer.getLoginName());
			ReflectUtil.voPersistence(transportLayer, alipayBusinessInfo);
		}else if(transportLayer.getPayType().equals(PayType.WX_PAY)){
			WxpayBusinessInfo wxpayBusinessInfo = wxpayBusinessInfoService.getInfoByLoginName(transportLayer.getLoginName());
			ReflectUtil.voPersistence(transportLayer, wxpayBusinessInfo);
		}
		logger.info("[tradelog"+transportLayer.getOrderNo()+"] set trade servicer success: " + transportLayer.getLoginName() + ",appid is : "+transportLayer.getAppid());
	}
	
	/**
	 * 设置交易收款方
	 * @param transportLayer
	 * @throws ReceiverException
	 */
	private void setTradeReceiver(TransportLayer transportLayer) throws ReceiverException {
	    // 设置交易 out trade no 为支付平台的订单号 退款跟退款查询不用设置
		if (!(transportLayer.getPayGate().equals(PayGate.TRADE_REFUND)
                || transportLayer.getPayGate().equals(PayGate.TRADE_REFUND_QUERY))) {
			transportLayer.setOutTradeNo(Utils.getRandomNum());
		}
		if (Validation.isEmpty(transportLayer.getSubAccountId())) { // 该支付直接由主账号收款，不用子账户
			return ;
		}
		if(transportLayer.getPayType().equals(PayType.ALI_PAY)){		//支付宝
			AlipaySubaccountInfo alipaySubaccountInfo = alipaySubAccountInfoService.getByLoginNameAndSubId(transportLayer.getSubAccountId(),transportLayer.getLoginName());
			if (!Validation.isEmpty(alipaySubaccountInfo)) {
				if (!Validation.isEmpty(alipaySubaccountInfo.getSellerId())) {
					logger.info("[tradelog"+transportLayer.getOrderNo()+"]seller id 被设置，seller id ： " + alipaySubaccountInfo.getSellerId());
					transportLayer.setTransOut(alipaySubaccountInfo.getSellerId()); // 分润设置
					transportLayer.setSellerId(alipaySubaccountInfo.getSellerId());
				} else if (!Validation.isEmpty(alipaySubaccountInfo.getStoreId())) {
					logger.info("[tradelog"+transportLayer.getOrderNo()+"]store id 被设置，store id ： " + alipaySubaccountInfo.getStoreId());
					transportLayer.setStoreId(alipaySubaccountInfo.getStoreId());
				}
			} else {
				throw new ReceiverException("[tradelog"+transportLayer.getOrderNo()+"]sellerId or storeId is null");
			}
		}else if(transportLayer.getPayType().equals(PayType.WX_PAY)){		//微信
			if(!Validation.isEmpty(transportLayer.getSubAccountId())){
				WxpaySubaccountInfo wxpaySubaccountInfo = wxpaySubaccountInfoService.getInfoBySubAccountId(transportLayer.getSubAccountId());
				if(!Validation.isEmpty(wxpaySubaccountInfo) && !Validation.isEmpty(wxpaySubaccountInfo.getSubMchid())){
					logger.info("[tradelog"+transportLayer.getOrderNo()+"]微信收款账户被设置,subMchid :" + wxpaySubaccountInfo.getSubMchid());
					transportLayer.setSubMchid(wxpaySubaccountInfo.getSubMchid());
				}
			}
		}
		
	}
	
	/**
	 * 保存tradeflow
	 * @return
	 * @throws Exception
	 */
	private void saveTradeFlow (TransportLayer transportLayer) throws Exception {
		if (transportLayer.getPayGate().equals(PayGate.QUERY_TRADE_RESULT)) { // 查询不保存流水
			return ;
		}
		// 在调用SDK前先保存流水信息
		TradeFlow tradeFlow = ReflectUtil.voPersistence(new TradeFlow(), transportLayer);
		tradeFlowService.saveOrUpdate(tradeFlow);
		transportLayer.setTradeFlowId(tradeFlow.getId().toString());
		logger.info("[tradelog"+transportLayer.getOrderNo()+"] save tradeFlow success ,tradeflowId is "+ transportLayer.getTradeFlowId());
	}
	
	/**
	 * 执行交易
	 * @param transportLayer
	 */
	private ResponseVo tradeService(TransportLayer transportLayer) {
		if (transportLayer.getPayType().equals(PayType.ALI_PAY)) {
			return (ResponseVo)ReflectUtil.excuteMethod(transportLayer.getReflectMethod(), alipayService, transportLayer, TransportLayer.class);
		} else if (transportLayer.getPayType().equals(PayType.WX_PAY)) {
			return (ResponseVo)ReflectUtil.excuteMethod(transportLayer.getReflectMethod(), wxpayService, transportLayer, TransportLayer.class);
		}
		return null;
	}
	
	/**
	 * 判断是否扫码支付，如果是则把二维码以流的形式直接返回
	 * @param transportLayer
	 * @param responseVo
	 * @param tradeFlowService2
	 * @param rep
	 * @return
	 * @throws Exception
	 */
	private boolean isQRCodePay(TransportLayer transportLayer,
			ResponseVo responseVo, TradeFlowService tradeFlowService2,HttpServletResponse rep) throws Exception {
		// 如果是二维码url，则直接用工具+resp写回页面
		if (transportLayer.getPayGate().equals(PayGate.QRCODE_PAY)) {
			try {
				GenerateQrCodeUtil.encodeQrcode(responseVo.getCodeUrl(), rep);
				logger.info("[tradelog"+transportLayer.getOrderNo()+"] qrcode return to rep success.");
			} catch (Exception e) {
				logger.error("[tradelog"+transportLayer.getOrderNo()+"] qrcode return error. exception is : " + e);
			}
			if (transportLayer.getPayType().equals(PayType.ALI_PAY)) {
				// 如果是支付宝扫码支付，则启动轮询线程，默认二维码生效时间：1m
				new QueryPayResultThread(transportLayer, tradeFlowService).start();
			}
			return true;
		} else {
			return false;
		}
	}
	/**
	 * @param req
	 * @param rep
	 * 用于接收微信回调信息
	 * @throws Exception 
	 */
	@RequestMapping(value="/wxNotifyInfo")
	public void notifyInfo(HttpServletRequest req,HttpServletResponse rep,String tradeFlowId) throws Exception{
		logger.info("接收回调信息成功！");
		logger.info("tradeFlowId : " + tradeFlowId);
		Scanner sc = new Scanner(req.getInputStream());
		String xml = "";
		while( sc.hasNextLine() ) {
			xml += sc.nextLine();
		}
		logger.info("xml : " + xml);
		sc.close();
		String resultCode = XmlUtil.getParam(xml, "result_code");
		String returnCode = XmlUtil.getParam(xml, "return_code");
		logger.info("resultCode" + resultCode);
		logger.info("returnCode" + returnCode);
		TransportLayer transportLayer = new TransportLayer(); 
		TradeFlow tradeFlow = null;
		String outTradeNo = "";
		if( "SUCCESS".equals(returnCode) && "SUCCESS".equals(resultCode) ) {
			outTradeNo = XmlUtil.getParam(xml, "out_trade_no");
			logger.info("outTradeNo : " + outTradeNo);
			TradeFlow params = new TradeFlow();params.setOutTradeNo(outTradeNo);
			tradeFlow = tradeFlowService.getTradeFlowByOutTradeNo(params);
			logger.info("tradeFlow json : " + JsonUtil.Object2Json(tradeFlow));
			logger.info("tradeFlow获取成功，loginName为 :" + tradeFlow.getLoginName());
			WxpayBusinessInfo wxpayBusinessInfo = wxpayBusinessInfoService.getInfoByLoginName(tradeFlow.getLoginName());
			logger.info("wxpayBusinessInfo json : " + JsonUtil.Object2Json(wxpayBusinessInfo));
			logger.info("get WxKey success wxkey is : " + wxpayBusinessInfo.getWxKey());
			transportLayer.setWxKey(wxpayBusinessInfo.getWxKey());
			if( SignUtil.checkVerify(xml,transportLayer) ) {
				setPayResult2Flow(PayResult.TRADE_SUCCESS, xml,tradeFlow);
				String requestUrl = "";
				if (!Validation.isEmpty(tradeFlow.getNotifyUrl())) {
					if (tradeFlow.getNotifyUrl().contains("?")) {
						requestUrl = tradeFlow.getNotifyUrl() + "&orderNo=" + tradeFlow.getOrderNo() + "&payResult=" + tradeFlow.getPayResult() + "&sign=" + SignUtil.MD5Sign(tradeFlow)  ;
					} else {
						requestUrl = tradeFlow.getNotifyUrl() + "?orderNo=" + tradeFlow.getOrderNo() + "&payResult=" + tradeFlow.getPayResult() + "&sign=" + SignUtil.MD5Sign(tradeFlow)  ;
					}
					HttpUtil.executeHttpGet(requestUrl);
				}
			}
		} else {
			setPayResult2Flow(PayResult.TRADE_FAIL, xml,tradeFlow);
		}
		
	}
	
	/**
	 * 支付宝刷卡支付回调地址
	 * @param req
	 * @param rep
	 * 用于接收回调信息
	 * @throws IOException 
	 */
	@SuppressWarnings("rawtypes")
	@RequestMapping(value="/aliNotifyInfo",produces="application/json;charset=UTF-8")
	public void notifyInfo(HttpServletRequest req,HttpServletResponse rep) throws IOException{
		logger.info("接收回调信息成功！");
		Map<String,String> params = new HashMap<String, String>();
		Map requestParams = req.getParameterMap();
		logger.info("支付宝post回调参数为："+ requestParams.toString());
		for (Iterator iter = requestParams.keySet().iterator();iter.hasNext();) {
			String name = (String)iter.next();
			String[] values = (String[]) requestParams.get(name);
			String valueStr = "";
			for (int i=0; i < values.length ; i++) {
				valueStr = (i==values.length-1)?valueStr + values[i] : valueStr + values[i] + ",";
			}
			// 乱码解决
			valueStr = new String(valueStr.getBytes("ISO-8859-1"),"UTF-8");
			logger.info("回调验证参数 requestParams :" + valueStr);
			params.put(name, valueStr);
		}
		// 订单号
		String outTradeNo = new String(req.getParameter("out_trade_no").getBytes("ISO-8859-1"),AlipayConfig.input_charset);
		// 交易状态
		String tradeStatus = new String(req.getParameter("trade_status").getBytes("ISO-8859-1"),AlipayConfig.input_charset);
		//支付宝公众号
		logger.info("回调参数接收成功，订单号 outTradeNo ："+ outTradeNo + "，交易状态 tradeStatus ："+ tradeStatus);
		boolean signVerified = false;
		try {
			String ALIPAY_PUBLIC_KEY = alipayBusinessInfoService.getPKByOutTradeNo(outTradeNo);
			logger.info("得到公钥："+ALIPAY_PUBLIC_KEY);
			signVerified = AlipaySignature.rsaCheckV1(params, ALIPAY_PUBLIC_KEY, AlipayConfig.input_charset); //调用SDK验证签名
			logger.info("验证算法成功执行，验证结果为：signVerified ："+signVerified);
		} catch (Exception e) {
			e.printStackTrace();
			logger.error("验证算法执行异常。异常信息："+ e);
		}
		
		if(signVerified){
		    // TODO 验签成功则继续业务操作，最后在response中返回success
			logger.info("验证成功，订单号："+outTradeNo + "入库");
			if (tradeStatus.equals("TRADE_SUCCESS") || tradeStatus.equals("TRADE_FINISHED")) {
				TradeFlow param = new TradeFlow(); param.setOutTradeNo(outTradeNo);
				TradeFlow tradeFlow = tradeFlowService.getTradeFlowByOutTradeNo(param);
				tradeFlow.setPayResult(PayResult.TRADE_SUCCESS);
				tradeFlowService.saveOrUpdate(tradeFlow);
			}
		}else{
		    // TODO 验签失败则记录异常日志，并在response中返回failure.
			List<TradeFlow> list = tradeFlowService.getTradeFlowByOrderId(outTradeNo);
			String notifyUrl = list.get(0).getNotifyUrl();
			logger.error("验证失败，回调客户的notify_url : "+ notifyUrl);
		}
		
	}
	
	
	private void setPayResult2Flow (String payResult,String xml,TradeFlow tradeFlow ) {
		String openId = XmlUtil.getParam(xml, "openid");
		tradeFlow.setPayResult(payResult);
		tradeFlow.setOpenId(openId);
		tradeFlowService.saveOrUpdate(tradeFlow);
		WxpayReturnInfo returnInfo =new WxpayReturnInfo();
		XmlUtil.xml2Entity(xml, returnInfo);
		returnInfo.setTradeFlowId(tradeFlow.getId());
		wxpayReturnInfoService.saveOrUpdate(returnInfo);
	}
	
}
